﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Objects;
using System.Linq;
using System.Text;
using System.Threading;
using System.Transactions;
using IBS.Bll.Accounts;
using IBS.Bll.Tools;
using IBS.Model;
using IBS.ServiceContracts;

namespace IBS.Bll.Transfers
{
    public class TransferService : ServiceBase, ITransferService
    {
        private readonly ICurrencyService _currencyService;
        private readonly IAccountService _accountService;
       
        #region Constructors

        public TransferService(IIbsContext context, ICurrencyService currencyService, IAccountService accountService)
            : base(context)
        {
            _currencyService = currencyService;
            _accountService = accountService;
        }

        #endregion

        private bool IsCurrencyOperation(Model.Transfer transfer)
        {
            return transfer.FromAccount.Currency.ISO != transfer.ToAccount.Currency.ISO;
        }

        private decimal GetReservedAmount(Transfer transfer)
        {
            var amount = transfer.FromAccountAmount;
            return amount;
        }

        private void CalculateAmounts(decimal amount, string ISO, string fromISO, string toISO, out decimal toAccountAmount,
                                    out decimal fromAccountAmount, out decimal? currencyRate)
        {

            if (fromISO != ISO && toISO != ISO)
                throw new BusinessException("Not correct operation currency");
            var operationCurrency = _currencyService.GetCurrency(ISO);
            var fromCurrency = _currencyService.GetCurrency(fromISO);
            var toCurrency = _currencyService.GetCurrency(toISO);

            var conversion = CurrencyConverter.Convert(amount, operationCurrency, fromCurrency, DateTime.Now);
            if (!conversion.IsSuccessfull)
                throw new BusinessException(conversion.Description);
            fromAccountAmount = conversion.Amount;
            conversion = CurrencyConverter.Convert(amount, operationCurrency, toCurrency, DateTime.Now);
            if (!conversion.IsSuccessfull)
                throw new BusinessException(conversion.Description);
            toAccountAmount = conversion.Amount;

            currencyRate = null;
            //if (fromISO == _currencyService.GetBaseCurrencyISO())
            //    currencyRate = _currencyService.GetBidRate(toISO);
            //else if (toISO == _currencyService.GetBaseCurrencyISO())
            //    _currencyService.GetAskRate(fromISO);
            var isCurrencyOperation = fromISO != ISO || toISO != ISO;
            if (isCurrencyOperation)
            {
                currencyRate = fromAccountAmount / toAccountAmount;
                if (currencyRate < 1)
                {
                    currencyRate = toAccountAmount / fromAccountAmount;

                }
            }
        }

        private Transfer CreateDepositTransfer(Card card, decimal amount)
        {
            var transfer = new Transfer()
            {
                TransferType = (int)TransferType.Deposit,
                FromAccount = _accountService.GetAssetsAccount(card.Account.Currency.ISO),//From Account
                ToAccount = card.Account,//ToAccount
                ToCard = card, //ToCard
                FromAccountAmount = amount,
                ToAccountAmount = amount,
                operationISO = card.Account.Currency.ISO,
                StartDate = DateTime.Now
            };
            return transfer;
        }

        private Transfer CreateWithdrawTransfer(Card card, decimal amount)
        {
            var transfer = new Transfer()
            {
                TransferType = (int)TransferType.Withdraw,
                FromCard = card,
                FromAccountAmount = amount,
                ToAccountAmount = amount,
                FromAccount = card.Account,
                ToAccount = _accountService.GetAssetsAccount(card.Account.Currency.ISO),
                operationISO = card.Account.Currency.ISO,
                StartDate = DateTime.Now
            };
            return transfer;
        }

        private Transfer CreateTransfer(Card fromCard, Card toCard, decimal amount, string ISO)
        {
            var fromISO = fromCard.Account.Currency.ISO;
            var toISO = toCard.Account.Currency.ISO;

            decimal toAccountAmount;
            decimal fromAccountAmount;
            decimal? currencyRate;
            CalculateAmounts(amount, ISO, fromISO, toISO, out toAccountAmount, out fromAccountAmount, out currencyRate);

            var transfer = new Transfer()
                               {
                                   CurrencyRate = currencyRate,
                                   TransferType = (int)TransferType.ToCard,
                                   FromCard = fromCard,
                                   operationISO = ISO,
                                   ToCard = toCard,
                                   FromAccount = fromCard.Account,
                                   ToAccount = toCard.Account,
                                   FromAccountAmount = fromAccountAmount,
                                   ToAccountAmount = toAccountAmount,
                                   StartDate = DateTime.Now,
                               };
            return transfer;
        }

        private Transfer CreateTransfer(Card fromCard, Account toAccount, decimal amount, string ISO)
        {
            var fromISO = fromCard.Account.Currency.ISO;
            var toISO = toAccount.Currency.ISO;

            decimal toAccountAmount;
            decimal fromAccountAmount;
            decimal? currencyRate;
            CalculateAmounts(amount, ISO, fromISO, toISO, out toAccountAmount, out fromAccountAmount, out currencyRate);

            var transfer = new Transfer()
                               {
                                   CurrencyRate = currencyRate,
                                   TransferType = (int)TransferType.ToAccount,
                                   FromCard = fromCard,
                                   operationISO = ISO,
                                   FromAccount = fromCard.Account,
                                   ToAccount = toAccount,
                                   FromAccountAmount = fromAccountAmount,
                                   ToAccountAmount = toAccountAmount,
                                   StartDate = DateTime.Now
                               };
            return transfer;
        }

        private Transfer CreateTransfer(Card fromCard, Payment toPayment, string paymentDetail,
                                                         decimal amount, string ISO)
        {
            var fromISO = fromCard.Account.Currency.ISO;
            var toISO = toPayment.Account.Currency.ISO;
            decimal toAccountAmount;
            decimal fromAccountAmount;
            decimal? currencyRate;
            CalculateAmounts(amount, ISO, fromISO, toISO, out toAccountAmount, out fromAccountAmount, out currencyRate);


            var transfer = new Transfer()
                               {
                                   CurrencyRate = currencyRate,
                                   TransferType = (int)TransferType.ToPayment,
                                   FromCard = fromCard,
                                   FromAccount = fromCard.Account,
                                   ToAccount = toPayment.Account,
                                   operationISO = ISO,
                                   Payment = toPayment,
                                   PaymentDetails = paymentDetail,
                                   FromAccountAmount = fromAccountAmount,
                                   ToAccountAmount = toAccountAmount,
                                   StartDate = DateTime.Now
                               };
            return transfer;
        }

        private Transfer CreateTransfer(Card fromCard, AutoPayment toAutoPayment, string paymentDetail, decimal amount, string ISO)
        {
            var fromISO = fromCard.Account.Currency.ISO;
            var toISO = toAutoPayment.Payment.Account.Currency.ISO;
            decimal toAccountAmount;
            decimal fromAccountAmount;
            decimal? currencyRate;
            Transfer transfer = null;
            try
            {
                CalculateAmounts(amount, ISO, fromISO, toISO, out toAccountAmount, out fromAccountAmount, out currencyRate);

                transfer = new Transfer()
                {
                    CurrencyRate = currencyRate,
                    TransferType = (int)TransferType.ToAutoPayment,
                    FromCard = fromCard,
                    FromAccount = fromCard.Account,
                    ToAccount = toAutoPayment.Payment.Account,
                    AutoPayment = toAutoPayment,
                    operationISO = ISO,
                    PaymentDetails = paymentDetail,
                    FromAccountAmount = fromAccountAmount,
                    ToAccountAmount = toAccountAmount,
                    StartDate = DateTime.Now
                };
            }
            catch(Exception)
            {
                if (transfer == null)

                    transfer = new Transfer()
                    {
                        CurrencyRate = 0,
                        TransferType = (int)TransferType.ToAutoPayment,
                        FromCard = fromCard,
                        FromAccount = fromCard.Account,
                        ToAccount = toAutoPayment.Payment.Account,
                        AutoPayment = toAutoPayment,
                        operationISO = ISO,
                        PaymentDetails = paymentDetail,
                        FromAccountAmount = 0, 
                        ToAccountAmount = 0,
                        StartDate = DateTime.Now
                    };
            }
            return transfer;
        }

        private void PendingTransfer(Transfer transfer)
        {
            transfer.Status = (int)TransferStatus.Pending;           
          
        }

        public IList<Transfer> GetCardTransfers(string cardNumber, Func<Transfer, bool> criteria = null, int? page = new int?())
        {
            return _context.Transfers.GetByCriteria(page, criteria).Where(t => (t.Card != null && t.Card.Number == cardNumber) || (t.ToCard != null && t.ToCard.Number == cardNumber)).ToList();
        }

        public Transfer Deposit(Card card, decimal amount)
        {
            var transfer = CreateDepositTransfer(card, amount);

            _accountService.ReserveAmount(transfer.FromAccount.Number, transfer.FromAccountAmount);
            PendingTransfer(transfer);
            _context.SaveChanges();
            return transfer;
        }

        public Transfer Withdraw(Card card, decimal amount)
        {
            var transfer = CreateWithdrawTransfer(card, amount);
            var reservedAmount = GetReservedAmount(transfer);
            Validate(card.Account, reservedAmount);
            _accountService.ReserveAmount(card.Account.Number, reservedAmount);
            PendingTransfer(transfer);
            _context.SaveChanges();
            return transfer;
        }

        public Transfer PayPayment(Card fromCard, Payment payment, string paymentDetail, decimal amount, string ISO, string useName)
        {
            var transfer = CreateTransfer(fromCard, payment, paymentDetail, amount, ISO);
            var reservedAmount = GetReservedAmount(transfer);

            Validate(fromCard.Account, reservedAmount);

            _accountService.ReserveAmount(fromCard.Account.Number, reservedAmount);

            PendingTransfer(transfer);
            _context.SaveChanges();
            return transfer;
        }

        public Transfer Transfer(Card fromCard, Card toCard, decimal amount, string ISO, string userName)
        {
            var transfer = CreateTransfer(fromCard, toCard, amount, ISO);
            var reservedAmount = GetReservedAmount(transfer);

            Validate(fromCard.Account, reservedAmount);

            _accountService.ReserveAmount(fromCard.Account.Number, reservedAmount);

            PendingTransfer(transfer);
            _context.SaveChanges();
            return transfer;
        }

        public Transfer Transfer(Card fromCard, Account toAccount, decimal amount, string ISO, string userName)
        {
            var transfer = CreateTransfer(fromCard, toAccount, amount, ISO);
            var reservedAmount = GetReservedAmount(transfer);

            Validate(fromCard.Account, reservedAmount);

            _accountService.ReserveAmount(fromCard.Account.Number, reservedAmount);

            PendingTransfer(transfer);
            _context.SaveChanges();
            return transfer;
        }

        private void FailTransfer(Transfer transfer)
        {
            transfer.Status = (int)TransferStatus.Fail;
            _context.SaveChanges();
        }

        public void PayAutoPayment(Card fromCard, AutoPayment toAutoPayment, string paymentDetail, string ISO, decimal amount)
        {
            Transfer transfer = null;
            try
            {
                transfer = CreateTransfer(fromCard, toAutoPayment, paymentDetail, amount, ISO);
                if (amount < 0)
                    throw new BusinessException("Amount must be positive");
                var reservedAmount = GetReservedAmount(transfer);

                Validate(fromCard.Account, reservedAmount);

                _accountService.ReserveAmount(fromCard.Account.Number, reservedAmount);
                if (transfer != null)
                    PendingTransfer(transfer);
            }
            catch (Exception exception)
            {
                //All errors for autoPayment must be registered
                if (transfer != null)
                    FailTransfer(transfer);
            }
            finally
            {
                try
                {
                    _context.SaveChanges();
                }
                catch (OptimisticConcurrencyException concurrency)
                {
                    _context.Refresh(RefreshMode.StoreWins, transfer.FromAccount);
                    _context.SaveChanges();
                }
            }
        }

        private void Validate(Account fromAccount, decimal expectedAmount)
        {
            if (!_accountService.AccountHasAmount(fromAccount, expectedAmount))
                throw new BusinessException("You don't have necessary amount");
        }

        public IList<Transfer> GetTransfers(Func<Transfer, bool> criteria = null, int? page = null)
        {
            var result = _context.Transfers.GetByCriteria(page, criteria);
            return result;
        }

        public void Wait()
        {
            Thread.Sleep(100);
        }


    }
}
